List & Label .NET
Programming Introduction / Other Important Concepts / Multithreading and Protection Job
In This Topic
    Multithreading and Protection Job
    In This Topic

    List & Label can be used from multiple threads. This enables the distribution of large print jobs on multiple different processors / cores. Internally, this is used for the designer preview or drill down reporting. If you plan to use List & Label in a multithreaded environment, keep the following issues in mind.

     

    List & Label Objects in Threads

    Make sure that each List & Label job (resp. a component instance) is only used within a single thread, i.e. the creation, usage and destruction of the job/component needs to be done from the same thread. If you want to use multiple printing threads, each of these threads needs to open and close it's own job. Background: Windows GDI resources like window handles or printer device contexts cannot be used across different threads.

     

    Protection Job

    Make sure to open a job/create a component instance in your application before starting the first print thread and do not close this job before all threads are terminated. Typically, this will be done in your application's start-up and shutdown code. Background: the first job creates a couple of helper objects that need to be destroyed in the same job. Also, this can increase the performance remarkably as it avoids steady loading and unloading of DLLs.

    Note: If List & Label is to work independently of printer drivers (usually in web applications etc.), the printerless mode can be activated for this purpose. The Printerless property must be set in the first object/protection job. A mixture of enabled and disabled printerless mode in the objects used in the application is not supported and can lead to unexpected behavior.

     

    Thread-Model

    Threads that open the designer need to use the Single Threaded Apartment (STA) concurrency model. This means you cannot use .NET worker threads from the thread pool for this task, as they are initialized to use the Multi Threaded Apartment (MTA) concurrency model. Background: List & Label needs to call OleInitialize() for drag & drop support within the designer, which requires the current apartment to be STA to succeed.

     

    Beispiel

    The following code snippet covers all the important points mentioned above and simulates starting Reporting.StartApplication() and ending Reporting.ExitApplication() of an application. It is intended solely for simplified illustration and must be adapted to the specific circumstances/application.

    It shows in detail how to use the protection job as _protectionJob. It is crucial that a new/fresh ListLabel instance (worker job) is always used for the main central functions Design, Print, or Export - the protection job is only kept in memory and is not actively used for any reporting functions.

    In addition, a central routine CreateListLabel() is used to create the required ListLabel instances, always using central basic options such as the LicensingInfo property. This also makes it easier to update when changing versions.

    ...
    
    // Use/simulation as application.
    
    
    // Application is started - protection job is created.
    Reporting myApp = new Reporting();
    myApp.StartApplication();
    
    // ...
    
    // Start the designer: A new worker job is created for this and released immediately.
    myApp.DesignReport();
    
    // ...
    
    // Start an export/print: A new worker job is created for this and released immediately.
    myApp.AnyFunctionWithinTheApplication();
    
    // ...
    
    // Application is being terminated - protection job is being released.
    myApp.ExitApplication();
    
    ...
    
    // Example of implementing a basic framework for using ListLabel instances.
    public class Reporting
    {
        // Global ListLabel instance (protection job) for module loading and caching.
        // This instance is *not* used for designing/exporting.
        private ListLabel? _protectionJob;
    
        // Main routine for starting the application.
        public void StartApplication()
        {
            // Creates the global protection job instance to load modules into memory
            // and build internal caches. This speeds up subsequent report processes.
            // Important: This object is no longer actively used in the course of the application.
            _protectionJob = helperCreateListLabel(isProtectionJob: true);
        }
    
        // Example method showing how a reporting process
        // can be started asynchronously in its own thread.
        public void AnyFunctionWithinTheApplication()
        {
            // Creates and starts a new thread that executes the ExecuteReport method.
            Thread newThread = new Thread(ExecuteReport);
            newThread.Start();
        }
    
        // Main routine for terminating the application.
        public void ExitApplication()
        {
            // Release protection job again so that caches and modules are unloaded.
            _protectionJob?.Dispose();
            _protectionJob = null;
        }
    
        // Executes a report as an export. 
        // A new worker job instance is created for each execution
        // so that running jobs do not influence each other.
        private void ExecuteReport()
        {
            // Worker job instance is short-lived and encapsulates all job-specific settings.
            using ListLabel worker = helperCreateListLabel(isProtectionJob: false);
    
            // Set export options, assign data source.
            // worker.ExportOptions.Add(LlExportOption.ExportTarget, "PDF");
            // worker.DataSource = myDataProvider;
            worker.DataSource = new List<string>() { "my values" };
    
            //worker.Export(...);
        }
    
        // Opens the designer for a report.
        // Uses a fresh worker job instance so that global caches remain untouched.
        public void DesignReport()
        {
            // Worker job instance is short-lived and encapsulates all job-specific settings.
            using ListLabel worker = helperCreateListLabel(isProtectionJob: false);
    
            // Assign data source(s), set various settings, etc.
            // worker.DataSource = myDataProvider;
            worker.DataSource = new List<string>() { "my values" };
    
            worker.Design();
        }
    
        // Creates a new ListLabel instance.
        // isProtectionJob
        // true: Instance serves exclusively as a protection job (modules/caching).
        // false: Instance is a short-lived worker job for design/print/export.
        private static ListLabel helperCreateListLabel(bool isProtectionJob)
        {
            var ll = new ListLabel
            {
                // Enter valid license information - see also redist.txt in the installation.
                LicensingInfo = ""
            };
    
            if (!isProtectionJob)
            {
                // Set worker job-specific default options here.
                // e.g. ll.Language = LlLanguage.German;
            }
            return ll;
        }
    }
    ...